package com.hyt.it.ogt.ykcj.service.zs.impl;

import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.StopWatch;
import cn.hutool.core.img.FontUtil;
import cn.hutool.core.img.Img;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapBuilder;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.*;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.hyt.core.base.BaseEntity;
import com.hyt.core.base.BaseServiceImpl;
import com.hyt.core.constant.CommonConstant;
import com.hyt.it.ogt.common.config.GlobalConfig;
import com.hyt.it.ogt.ykcj.common.annotation.zs.TemplateMapper;
import com.hyt.it.ogt.ykcj.common.enums.zs.*;
import com.hyt.it.ogt.ykcj.common.exception.CustomException;
import com.hyt.it.ogt.ykcj.common.exception.InvalidRequestException;
import com.hyt.it.ogt.ykcj.domain.vo.ExamInfoVo;
import com.hyt.it.ogt.ykcj.domain.zs.*;
import com.hyt.it.ogt.ykcj.domain.zs.dto.*;
import com.hyt.it.ogt.ykcj.domain.zs.vo.ZsExamineeInfoVO;
import com.hyt.it.ogt.ykcj.mapper.zs.ZsExamineeInfoMapper;
import com.hyt.it.ogt.ykcj.service.exam.IExamInfoService;
import com.hyt.it.ogt.ykcj.service.system.IFileUpHwCloudService;
import com.hyt.it.ogt.ykcj.service.zs.*;
import com.hyt.it.ogt.ykcj.utils.ExecutorPoolUtil;
import com.hyt.it.ogt.ykcj.utils.ImageMarkUtils;
import com.hyt.loginfo.service.IBaseLoginService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.awt.*;
import java.io.File;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.List;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 证书考生信息接口
 *
 * @author admin
 * @date 2021-08-10
 */
@Slf4j
@Service
public class ZsExamineeInfoServiceImpl extends BaseServiceImpl<ZsExamineeInfoMapper, ZsExamineeInfo> implements IZsExamineeInfoService {

    Cache<String, Font> timedCache = CacheUtil.newTimedCache(-1);
    @Resource
    GlobalConfig globalConfig;
    @Resource
    IZsInfoService infoService;
    @Resource
    IZsTemplateService templateService;
    @Resource
    IZsTemplateConfigService templateConfigService;
    @Resource
    IZsExamineeExtInfoService examineeExtInfoService;
    @Resource
    IFileUpHwCloudService fileUpHwCloudService;
    @Resource
    IBaseLoginService loginService;
    @Resource
    private ZsExamineeInfoMapper zsExamineeInfoMapper;
    @Resource
    private IZsInfoService iZsInfoService;
    @Resource
    IExamInfoService examInfoService;

    /**
     * @Params:
     * @Description: 查询考生证书列表
     * @Author: STARF
     * @CreateDate: 2022/6/24 11:16
     **/
    @Override
    public <E extends IPage<ZsExamineeInfoVO>> E examineePage(E page, String zsId, String idWord, Integer status) {
        List<ZsExamineeInfo> zsExamineeInfos = zsExamineeInfoMapper.selectZsExamineeInfoPage(zsId, idWord, status);

        ZsInfo zsInfo = iZsInfoService.getById(zsId);
        if (zsInfo == null) {
            throw new CustomException("无效的证书信息");
        }
        // 获取模板列表渲染配置
        List<ZsTemplateConfig> zsTemplateConfigs = templateConfigService.listShowViewConfig(zsInfo.getCertificateTemplateid());
        // 将不允许使用默认值的配置项去掉默认值
        zsTemplateConfigs.stream()
                .filter(data -> !data.getAllowUseDefault())
                .forEach(templateConfig -> templateConfig.setVariableDefaultValue(null));

        List<ZsExamineeInfoVO> voList = new ArrayList<>();
        for (ZsExamineeInfo info : zsExamineeInfos) {
            ZsExamineeInfoVO vo = BeanUtil.toBean(info, ZsExamineeInfoVO.class);
            vo.setIdentificationTypeDesc(IdentificationType.getDescByCode(info.getIdentificationType()));
            vo.setCertificateProvideStatusDesc(CertificateProvideStatusEnums.getByCode(info.getCertificateProvideStatus()).getDesc());

            // 证书有效期描述
            String certificateValidityStart = DateUtil.format(info.getCertificateValidityStart(), DatePattern.NORM_DATE_FORMAT);
            String certificateValidityEnd = DateUtil.format(info.getCertificateValidityEnd(), DatePattern.NORM_DATE_FORMAT);
            String certificateValidityDesc = StrUtil.format("{} - {}",
                    Optional.ofNullable(certificateValidityStart)
                            .orElse(StrPool.DASHED),
                    Optional.ofNullable(certificateValidityEnd)
                            .orElse(StrPool.DASHED));
            vo.setCertificateValidityDesc(certificateValidityDesc);

            // 性别
            Optional.ofNullable(info.getExamineeSex())
                    .map(SexEnums::getDescByType)
                    .ifPresent(vo::setExamineeSexDesc);

            List<ZsExamineeExtInfo> examineeExtInfos = examineeExtInfoService.listByExamineeId(info.getId());
            // 装配模板配置的最终渲染可用集合
            List<ZsTemplateConfig> examineeTemplateConfig = assembleTemplateConfig(vo, zsTemplateConfigs, examineeExtInfos);
            Map<String, String> showViewTemplateValue = examineeTemplateConfig.stream()
                    .filter(data -> StrUtil.isNotBlank(data.getVariableCode()))
                    .filter(data -> StrUtil.isNotBlank(data.getVariableDefaultValue()))
                    .collect(Collectors.toMap(ZsTemplateConfig::getVariableCode, ZsTemplateConfig::getVariableDefaultValue));
            vo.setShowViewTemplateValue(showViewTemplateValue);

            voList.add(vo);
        }
        Integer total = zsExamineeInfoMapper.countZsExamineeInfoByZsId(zsId, idWord, status);
        page.setTotal(total);
        page.setRecords(voList);
        return page;
    }

    @Override
    public Boolean issueCertificate(String examineeInfoId) {
        ZsExamineeInfo examineeInfo = Optional.ofNullable(this.getById(examineeInfoId))
                .orElseThrow(() -> new CustomException("未查询到当前考生的信息"));
        if (!NumberUtil.equals(examineeInfo.getCertificateProvideStatus(), CertificateProvideStatusEnums.UNPROVIDED.getCode())) {
            throw new CustomException("当前考生证书已发放，请勿重复发放");
        }
        Optional.ofNullable(examineeInfo.getCertificateSerialNumber())
                .filter(StrUtil::isNotBlank)
                .orElseThrow(() -> new CustomException("当前考生的证书还未导入证书编码，不允许进行发放证书操作"));

        ZsInfo zsInfo = Optional.ofNullable(infoService.getById(examineeInfo.getZsId()))
                .orElseThrow(() -> new CustomException("未查询到当前所属的证书信息"));

        ZsTemplate zsTemplate = Optional.ofNullable(templateService.getById(zsInfo.getCertificateTemplateid()))
                .orElseThrow(() -> new CustomException("未查询到当前所属的证书模板"));

        List<ZsTemplateConfig> zsTemplateConfigs = templateConfigService.listTemplateRenderConfig(zsTemplate.getId());
        if (CollUtil.isEmpty(zsTemplateConfigs)) {
            throw new CustomException("未查询到当前所属的证书模板配置信息");
        }

        return issueCertificate(examineeInfo, zsTemplate, zsTemplateConfigs, loginService.getUserId());
    }

    @Override
    public Boolean issueCertificate(ZsExamineeInfo examineeInfo, ZsTemplate zsTemplate, List<ZsTemplateConfig> zsTemplateConfigs, String updateBy) {
        String watchName = StrUtil.format("{}:{}", zsTemplate.getTemplateName(), examineeInfo.getExamineeName());
        StopWatch stopWatch = StopWatch.create(watchName);

        // 获取证书生成文件
        List<ZsTemplateConfig> certificateFileConfig = BeanUtil.copyToList(zsTemplateConfigs, ZsTemplateConfig.class);
        CompletableFuture<File> certificateFileFuture = CompletableFuture.supplyAsync(() -> this.createCertificate(examineeInfo, zsTemplate, certificateFileConfig),
                ExecutorPoolUtil.getPoolTaskExecutor());
        // 这里2个异步操作占总操作时长的40%
        CompletableFuture<String> pdfUrlFuture = certificateFileFuture.thenApplyAsync(certificateFile -> this.uploadPdfFile(certificateFile, examineeInfo.getExamineeName(), examineeInfo.getIdentificationNo()),
                ExecutorPoolUtil.getPoolTaskExecutor());
        CompletableFuture<String> imgUrlFuture = certificateFileFuture.thenApplyAsync(certificateFile -> this.getWaterImageUrl(certificateFile, examineeInfo.getExamineeName(), examineeInfo.getIdentificationNo()),
                ExecutorPoolUtil.getPoolTaskExecutor());

        // 去掉签章的配置
        List<ZsTemplateConfig> noSignatureConfig = BeanUtil.copyToList(zsTemplateConfigs, ZsTemplateConfig.class);
        noSignatureConfig.removeIf(zsTemplateConfig -> StrUtil.equals(zsTemplateConfig.getVariableCode(), VariableCodeEnums.SIGNATURE.name()));
        // 创建没有电子签章的证书
        CompletableFuture<File> noSignatureFileFuture = CompletableFuture.supplyAsync(() -> this.createCertificate(examineeInfo, zsTemplate, noSignatureConfig),
                ExecutorPoolUtil.getPoolTaskExecutor());
        CompletableFuture<String> noSignaturePdfUrlFuture = noSignatureFileFuture.thenApplyAsync(certificateFile -> this.uploadPdfFile(certificateFile, examineeInfo.getExamineeName(), examineeInfo.getIdentificationNo()),
                ExecutorPoolUtil.getPoolTaskExecutor());
        CompletableFuture<String> noSignatureImgUrlFuture = noSignatureFileFuture.thenApplyAsync(certificateFile -> this.getWaterImageUrl(certificateFile, examineeInfo.getExamineeName(), examineeInfo.getIdentificationNo()),
                ExecutorPoolUtil.getPoolTaskExecutor());

        stopWatch.start("异步生成考生文件");
        CompletableFuture.allOf(pdfUrlFuture, imgUrlFuture, noSignaturePdfUrlFuture, noSignatureImgUrlFuture)
                .join();
        stopWatch.stop();
        log.debug("考生准考证生成统计：{}", stopWatch.prettyPrint(TimeUnit.MILLISECONDS));
        
        // 删除生成的基础文件
        certificateFileFuture.thenAccept(File::delete);
        noSignatureFileFuture.thenAccept(File::delete);

        String pdfUrl = pdfUrlFuture.join();
        String imgUrl = imgUrlFuture.join();
        String noSignaturePdfUrl = noSignaturePdfUrlFuture.join();
        String noSignatureImgUrl = noSignatureImgUrlFuture.join();

        return this.lambdaUpdate()
                .set(ZsExamineeInfo::getCertificateImgUrl, imgUrl)
                .set(ZsExamineeInfo::getCertificatePdfUrl, pdfUrl)
                .set(ZsExamineeInfo::getNoSignatureImgUrl, noSignatureImgUrl)
                .set(ZsExamineeInfo::getNoSignaturePdfUrl, noSignaturePdfUrl)
                .set(ZsExamineeInfo::getCertificateProvideStatus, CertificateProvideStatusEnums.PROVIDED.getCode())
                .set(BaseEntity::getUpdateDate, LocalDateTime.now())
                .set(BaseEntity::getUpdateBy, updateBy)
                .eq(BaseEntity::getId, examineeInfo.getId())
                .update();
    }

    private String uploadImgFile(File certificateFile, String examineesName, String identificationNo) {
        String imgFileName = StrUtil.format("{}_{}_{}.png", examineesName, identificationNo, System.currentTimeMillis());
        return fileUpHwCloudService.uploadFileToHuawei(certificateFile, "certificate", imgFileName);
    }

    private String uploadPdfFile(File certificateFile, String examineesName, String identificationNo) {
        String pdfFileName = StrUtil.format("{}_{}_{}.pdf", examineesName, identificationNo, System.currentTimeMillis());
        File pdfFile = ImageMarkUtils.imgToPdf(certificateFile);
        String certificateUrl = fileUpHwCloudService.uploadFileToHuawei(pdfFile, "certificate", pdfFileName);
        pdfFile.delete();
        return certificateUrl;
    }

    private File createCertificate(ZsExamineeInfo examineeInfo, ZsTemplate zsTemplate, List<ZsTemplateConfig> zsTemplateConfigs) {
        Assert.notNull(examineeInfo, "考生信息不能为空");
        List<ZsExamineeExtInfo> examineeExtInfos = examineeExtInfoService.listByExamineeId(examineeInfo.getId());

        return createCertificate(examineeInfo, zsTemplate, zsTemplateConfigs, examineeExtInfos);
    }

    private File createCertificate(ZsExamineeInfo examineeInfo, ZsTemplate zsTemplate, List<ZsTemplateConfig> zsTemplateConfigs, List<ZsExamineeExtInfo> examineeExtInfos) {
        Assert.notNull(examineeInfo, "考生信息不能为空");
        Assert.notNull(zsTemplate, "模板信息不能为空");
        Assert.notEmpty(zsTemplateConfigs, "模板配置信息不能为空");
        Assert.notNull(examineeExtInfos, "考生拓展信息不能为空");

        // 将不允许使用默认值的配置项去掉默认值
        zsTemplateConfigs.stream()
                .filter(data -> !data.getAllowUseDefault())
                .forEach(templateConfig -> templateConfig.setVariableDefaultValue(null));

        ZsExamineeInfoDTO zsExamineeInfoDTO = assembleExamineeInfoDTO(examineeInfo);
        assembleExamineesName(zsExamineeInfoDTO::getExamineeName, zsExamineeInfoDTO::setExamineeName);

        // 装配模板配置的最终渲染可用集合
        List<ZsTemplateConfig> examineeTemplateConfig = assembleTemplateConfig(zsExamineeInfoDTO, zsTemplateConfigs, examineeExtInfos);

        // 装配生成模板使用的实体
        List<ImageBaseDTO> templateImageDTO = assembleImageBaseDTO(examineeTemplateConfig);
        File file = ImageMarkUtils.writeImage(zsTemplate.getTemplateUrl(), templateImageDTO);
        return file;
    }

    /**
     * 装配模板配置的最终渲染可用集合
     *
     * @param examineeInfo
     * @param zsTemplateConfigs
     * @param examineeExtInfos
     * @return
     */
    private List<ZsTemplateConfig> assembleTemplateConfig(Object examineeInfo, List<ZsTemplateConfig> zsTemplateConfigs, List<ZsExamineeExtInfo> examineeExtInfos) {
        // Map<变量code, 变量值>
        Map<String, Object> extTemplateValue = assembleExamineeExtInfos(examineeExtInfos);
        // Map<变量code, 变量值>
        Map<String, Object> entityTemplateValue = getEntityTemplateValue(examineeInfo);
        // 合并Map，entityTemplateValue优先级大于extTemplateValue，故放在后面合并
        extTemplateValue.putAll(entityTemplateValue);

        List<ZsTemplateConfig> examineeTemplateConfig = BeanUtil.copyToList(zsTemplateConfigs, ZsTemplateConfig.class);
        examineeTemplateConfig.forEach(data -> {
            ImportCheckTypeEnums importCheckTypeEnums = ImportCheckTypeEnums.getByCode(data.getImportCheckType());

            Optional.ofNullable(data.getVariableCode())
                    .map(extTemplateValue::get)
                    .filter(ObjectUtil::isNotEmpty)
                    // 转换导入数据类型
                    .map(variable -> this.castObjectTypeValue(variable, importCheckTypeEnums, data))
                    .filter(StrUtil::isNotBlank)
                    .ifPresent(data::setVariableDefaultValue);
        });
        return examineeTemplateConfig;
    }

    /**
     * 装配考生额外信息为Map结构
     *
     * @param examineeExtInfos
     * @return
     */
    private Map<String, Object> assembleExamineeExtInfos(List<ZsExamineeExtInfo> examineeExtInfos) {
        Map<String, Object> extTemplateValue = examineeExtInfos.stream()
                .filter(data -> StrUtil.isNotBlank(data.getVariableCode()))
                .collect(Collectors.toMap(ZsExamineeExtInfo::getVariableCode, ZsExamineeExtInfo::getVariableValue));
        return extTemplateValue;
    }

    /**
     * 装配考生姓名的规则
     *
     * @param getExamineeName
     * @param setExamineeName
     */
    private void assembleExamineesName(Supplier<String> getExamineeName, Consumer<String> setExamineeName) {
        Optional.ofNullable(getExamineeName)
                .map(Supplier::get)
                .filter(StrUtil::isNotBlank)
                .map(data -> {
                    if (StrUtil.length(data) == CommonConstant.TWO) {
                        return new StringBuilder(data)
                                .insert(CommonConstant.ONE, new char[]{StrPool.C_SPACE, StrPool.C_SPACE}, CommonConstant.ZERO, CommonConstant.TWO)
                                .toString();
                    }
                    return data;
                }).ifPresent(setExamineeName);
    }

    public ZsExamineeInfoDTO assembleExamineeInfoDTO(ZsExamineeInfo zsExamineeInfo) {
        ZsExamineeInfoDTO zsExamineeInfoDTO = BeanUtil.toBean(zsExamineeInfo, ZsExamineeInfoDTO.class);
        Optional.ofNullable(zsExamineeInfoDTO.getIdentificationType())
                .map(IdentificationType::getDescByCode)
                .ifPresent(zsExamineeInfoDTO::setIdentificationTypeDesc);
        // 性别
        Optional.ofNullable(zsExamineeInfo.getExamineeSex())
                .map(SexEnums::getDescByType)
                .ifPresent(zsExamineeInfoDTO::setExamineeSexDesc);
        return zsExamineeInfoDTO;
    }

    /**
     * 装配生成模板使用的实体
     *
     * @param examineeTemplateConfig
     * @return
     */
    public List<ImageBaseDTO> assembleImageBaseDTO(List<ZsTemplateConfig> examineeTemplateConfig) {
        log.debug("# 发放证书装配模板实体参数：{}", JSON.toJSONString(examineeTemplateConfig, SerializerFeature.WriteMapNullValue));

        List<ImageBaseDTO> imageBaseDTOS = examineeTemplateConfig.stream()
                .filter(data -> StrUtil.isNotBlank(data.getVariableDefaultValue()))
                .map(data -> {
                    Color color = Optional.ofNullable(data.getVariableColor())
                            .map(colorHex -> colorHex.substring(CommonConstant.ONE))
                            .map(colorHex -> Integer.parseInt(colorHex, 16))
                            .map(Color::new)
                            .orElse(null);

                    ImageBaseDTO baseDTO;
                    switch (VariableTypeEnums.getByCode(data.getVariableType())) {
                        case TEXT:
                            Font font = getFontByUrl(data.getVariableFont());
                            font = font.deriveFont(Font.PLAIN, data.getVariableFontSize());

                            baseDTO = new TextInfoDTO()
                                    .setText(data.getVariableDefaultValue())
                                    .setColor(color)
                                    .setFont(font)
                                    .setX(data.getVariableXPoint())
                                    .setY(data.getVariableYPoint())
                                    .setWidth(data.getVariableWidth())
                                    .setHeight(data.getVariableHeight())
                                    .setIsCenter(data.getVariableCenter());
                            break;
                        case IMAGE:
                            baseDTO = new ImageInfoDTO()
                                    .setUrl(data.getVariableDefaultValue())
                                    .setColor(color)
                                    .setHeight(data.getVariableHeight())
                                    .setWidth(data.getVariableWidth())
                                    .setX(data.getVariableXPoint())
                                    .setY(data.getVariableYPoint())
                                    .setIsCenter(data.getVariableCenter());
                            break;
                        default:
                            baseDTO = new ImageBaseDTO();
                            break;
                    }
                    return baseDTO;
                }).collect(Collectors.toList());
        log.debug("# 发放证书装配生成图片参数：{}", JSON.toJSONString(imageBaseDTOS, SerializerFeature.WriteMapNullValue));
        return imageBaseDTOS;
    }

    /**
     * 通过字体url获取字体
     *
     * @param fontUrl
     * @return
     */
    private Font getFontByUrl(String fontUrl) {
        Font font = timedCache.get(fontUrl);
        if (ObjectUtil.isEmpty(font)) {
            File fontTempFile = FileUtil.createTempFile();
            HttpUtil.downloadFile(fontUrl, fontTempFile);
            font = FontUtil.createFont(fontTempFile);
            timedCache.put(fontUrl, font);
        }
        return font;
    }

    /**
     * 解析实体，获取实体内字段上的模板参数值
     *
     * @param examineeInfo
     * @return
     */
    private Map<String, Object> getEntityTemplateValue(Object examineeInfo) {
        // Map<变量code, 变量值>
        return Arrays.stream(ReflectUtil.getFields(examineeInfo.getClass()))
                .filter(this::filterTemplateMapperAnnotation)
                .map(field -> {
                    TemplateMapper annotation = field.getAnnotation(TemplateMapper.class);
                    String[] templateCodes = annotation.templateCode();
                    MapBuilder<String, Object> builder = MapUtil.builder();
                    Object fieldValue = ReflectUtil.getFieldValue(examineeInfo, field);
                    Arrays.stream(templateCodes)
                            .forEach(data -> builder.put(data, fieldValue));
                    return builder.build();
                }).map(Map::entrySet)
                .flatMap(Collection::stream)
                .filter(data -> ObjectUtil.isNotEmpty(data.getValue()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    /**
     * 转换数据类型参数
     *
     * @param fieldValue
     * @return
     */
    private String castObjectTypeValue(Object fieldValue, ImportCheckTypeEnums importCheckTypeEnums, ZsTemplateConfig templateConfig) {
        String value;
        String templateFormat = templateConfig.getTemplateFormat();
        // 如果模板格式不为空
        if (StrUtil.isNotBlank(templateFormat)) {
            // 如果参数类型为日期
            if (fieldValue instanceof Date) {
                value = DateUtil.format((Date) fieldValue, templateFormat);
                // 如果为字符串，且导入类型为日期
            } else if (importCheckTypeEnums.equals(ImportCheckTypeEnums.DATE)) {
                DateTime dateTime = DateUtil.parseDate((CharSequence) fieldValue);
                value = DateUtil.format(dateTime, templateFormat);
            } else {
                value = StrUtil.format(templateFormat, fieldValue);
            }
        } else {
            // 如果参数类型为日期
            if (fieldValue instanceof Date) {
                value = DateUtil.formatChineseDate((Date) fieldValue, Boolean.FALSE, Boolean.FALSE);
                // 如果为字符串，且导入类型为日期，请注意，这里可能会导致2023/6这样的缺少（日)期的格式化失败
            } else if (importCheckTypeEnums.equals(ImportCheckTypeEnums.DATE)) {
                DateTime dateTime = DateUtil.parseDate((CharSequence) fieldValue);
                value = DateUtil.formatChineseDate(dateTime, Boolean.FALSE, Boolean.FALSE);
            } else {
                value = (String) fieldValue;
            }
        }
        return value;
    }

    private Boolean filterTemplateMapperAnnotation(Field field) {
        TemplateMapper annotation = field.getAnnotation(TemplateMapper.class);
        return ObjectUtil.isNotEmpty(annotation) && ArrayUtil.isNotEmpty(annotation.templateCode());
    }


    /**
     * @Params:
     * @Description: 删除考生
     * @Author: STARF
     * @CreateDate: 2022/6/24 14:04
     **/
    @Override
    public Integer deleteExaminee(String id, String updateBy) {
        ZsExamineeInfo zsExamineeInfo = zsExamineeInfoMapper.selectById(id);
        ZsInfo zsInfo = iZsInfoService.getById(zsExamineeInfo.getZsId());

        //导入的证书人员才能删除
        if (CertificateDataSourceEnums.RELATE_CJ.getCode().equals(zsInfo.getCertificateDataSource())) {
            return 52403021;
        }

        //已发放的不能删除
        if (CertificateProvideStatusEnums.PROVIDED.getCode().equals(zsExamineeInfo.getCertificateProvideStatus())) {
            return 52403022;
        }

        //证书已发布的不能删除
        if (CertificateStatusEnums.PUBLISHED.getCode().equals(zsInfo.getCertificateStatus())) {
            return 52403023;
        }

        zsExamineeInfo.setUpdateDate(LocalDateTime.now());
        zsExamineeInfo.setUpdateBy(updateBy);
        zsExamineeInfo.setDelFlag(true);
        zsExamineeInfoMapper.updateById(zsExamineeInfo);
        return 20000;
    }

    /**
     * @Params:
     * @Description: 证书查询
     * @Author: STARF
     * @CreateDate: 2022/6/24 16:24
     **/
    @Override
    public ZsExamineeInfoVO queryZsExaminee(String zsId, String name, String idCardNo) throws Exception {
        ZsExamineeInfo zsExamineeInfo = zsExamineeInfoMapper.getZsExamineeInfoByAdmissionNo(zsId, name, null, null);
        if (null == zsExamineeInfo) {
            throw new InvalidRequestException(52403035, "姓名不正确！");
        }
        zsExamineeInfo = zsExamineeInfoMapper.getZsExamineeInfoByAdmissionNo(zsId, null, idCardNo, idCardNo);
        if (null == zsExamineeInfo) {
            throw new InvalidRequestException(52403036, "准考证号或证件号不正确！");
        }

        ZsInfo zsInfo = iZsInfoService.getById(zsExamineeInfo.getZsId());
        if (CertificateStatusEnums.UNPUBLISHED.getCode().equals(zsInfo.getCertificateStatus())) {
            throw new InvalidRequestException(52403034, "证书尚未发布，请耐心等待！");
        }

        zsExamineeInfo = zsExamineeInfoMapper.getZsExamineeInfoByAdmissionNo(zsId, name, idCardNo, idCardNo);
        if (null == zsExamineeInfo) {
            throw new InvalidRequestException(52403033, "很遗憾，您未获得证书！");
        }

        if (CertificateProvideStatusEnums.UNPROVIDED.getCode().equals(zsExamineeInfo.getCertificateProvideStatus())) {
            throw new InvalidRequestException(52403032, "很遗憾，您未获得证书！");
        }

        // 校验证书的有效时间
        ZsExamineeInfoVO vo = BeanUtil.toBean(zsExamineeInfo, ZsExamineeInfoVO.class);
        vo.setIdentificationTypeDesc(IdentificationType.getDescByCode(zsExamineeInfo.getIdentificationType()));
        vo.setCertificateProvideStatusDesc(CertificateProvideStatusEnums.getByCode(zsExamineeInfo.getCertificateProvideStatus()).getDesc());
        // 性别
        Optional.ofNullable(vo.getExamineeSex())
                .map(SexEnums::getDescByType)
                .ifPresent(vo::setExamineeSexDesc);
        
        Optional.ofNullable(zsInfo.getExamId())
                .map(examInfoService::selectExamById)
                .ifPresent(data -> {
                    vo.setExamStartDay(data.getExamStartDay());
                    vo.setExamEndDay(data.getExamEndDay());
                });

        Date certificateValidityEnd = vo.getCertificateValidityEnd();
        Date now = new Date();
        if (now.compareTo(certificateValidityEnd) >= 0) {
            throw new InvalidRequestException(52403035, "很遗憾，证书已经过了有效期！");
        }
        // 查询考试信息
        if (StringUtils.isNoneBlank(zsInfo.getExamId())) {
            // 查询考试名称
            ExamInfoVo examInfo = examInfoService.selectExamInfoById(zsInfo.getExamId());
            if (null != examInfo) {
                vo.setExamId(examInfo.getId());
                vo.setExamName(examInfo.getExamNameConfigName());
            }
        }
        return vo;
    }


    /**
     * @Params:
     * @Description: 根据考试id，准考证号和身份证号码查询考生证书消息
     * @Author: huangwu
     * @CreateDate: 2022/7/15 16:24
     **/
    @Override
    public ZsExamineeInfoVO queryZsExamineeByExamId(String examId, String admissonNo, String idCardNo) throws Exception {
        ZsExamineeInfo zsExamineeInfo = zsExamineeInfoMapper.queryZsExamineeByExamId(examId, admissonNo, idCardNo);
        if (null == zsExamineeInfo) {
            throw new InvalidRequestException(52403033, "考生证书信息不存在");
        }

        ZsExamineeInfoVO vo = BeanUtil.toBean(zsExamineeInfo, ZsExamineeInfoVO.class);


        ZsInfo zsInfo = iZsInfoService.getById(zsExamineeInfo.getZsId());
        if (CertificateStatusEnums.UNPUBLISHED.getCode().equals(zsInfo.getCertificateStatus())) {
            throw new InvalidRequestException(52403034, "当前考生绑定的证书未发布");
        }


        vo.setIdentificationTypeDesc(IdentificationType.getDescByCode(zsExamineeInfo.getIdentificationType()));
        vo.setCertificateProvideStatusDesc(CertificateProvideStatusEnums.getByCode(zsExamineeInfo.getCertificateProvideStatus()).getDesc());
        // 性别
        Optional.ofNullable(vo.getExamineeSex())
                .map(SexEnums::getDescByType)
                .ifPresent(vo::setExamineeSexDesc);
        
        Optional.ofNullable(zsInfo.getExamId())
                .map(examInfoService::selectExamById)
                .ifPresent(data -> {
                    vo.setExamStartDay(data.getExamStartDay());
                    vo.setExamEndDay(data.getExamEndDay());
                });

        return vo;
    }


    /**
     * @Params:
     * @Description: 证书查询
     * @Author: STARF
     * @CreateDate: 2022/6/24 16:24
     **/
    @Override
    public ZsExamineeInfoVO queryZsExamineeById(String id, String zsId) throws Exception {
        QueryWrapper<ZsExamineeInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().eq(ZsExamineeInfo::getId, id).or().eq(ZsExamineeInfo::getZsId, zsId);
        ZsExamineeInfo zsExamineeInfo = zsExamineeInfoMapper.selectOne(queryWrapper);
        if (null == zsExamineeInfo) {
            return null;
        }
        ZsExamineeInfoVO vo = BeanUtil.toBean(zsExamineeInfo, ZsExamineeInfoVO.class);
        vo.setIdentificationTypeDesc(IdentificationType.getDescByCode(zsExamineeInfo.getIdentificationType()));
        vo.setCertificateProvideStatusDesc(CertificateProvideStatusEnums.getByCode(zsExamineeInfo.getCertificateProvideStatus()).getDesc());
        // 性别
        Optional.ofNullable(vo.getExamineeSex())
                .map(SexEnums::getDescByType)
                .ifPresent(vo::setExamineeSexDesc);
        
        return vo;
    }

    /**
     * 获取水印图片url
     * ps：有水印和无水印生成耗时10倍差距
     */
    private String getWaterImageUrl(File certificateFile, String examineesName, String identificationNo) {
        if (globalConfig.getHasWaterFont()) {
            String fileType = FileUtil.getSuffix(certificateFile);
            File tempFile = FileUtil.createTempFile(StrPool.DOT + fileType, Boolean.TRUE);
            tempFile.deleteOnExit();
            Img.from(certificateFile)
                    .setTargetImageType(fileType)
                    .pressTextFull(globalConfig.getWaterMarkDesc(), Color.LIGHT_GRAY,
                            getFontByUrl(globalConfig.getWaterFontUrl()).deriveFont(Font.PLAIN, 30),
                            4,
                            30,
                            0.8f)
                    .write(tempFile);
            String imgUrl = this.uploadImgFile(tempFile, examineesName, identificationNo);
            tempFile.delete();
            return imgUrl;
        } else {
            return this.uploadImgFile(certificateFile, examineesName, identificationNo);
        }
    }

    @Override
    public List<ZsExamineeInfo> releasableExamineesInfo(String infoId) {
        Assert.notBlank(infoId, "证书信息不存在");
        return this.lambdaQuery()
                .eq(ZsExamineeInfo::getZsId, infoId)
                .isNotNull(ZsExamineeInfo::getCertificateSerialNumber)
                .eq(ZsExamineeInfo::getCertificateProvideStatus, CertificateProvideStatusEnums.UNPROVIDED.getCode())
                .eq(BaseEntity::getDelFlag, Boolean.FALSE)
                .list();
    }

    @Override
    public List<ZsExamineeInfo> allExamineesInfo(String infoId) {
        Assert.notBlank(infoId, "证书信息不存在");
        return this.lambdaQuery()
                .eq(ZsExamineeInfo::getZsId, infoId)
                .eq(BaseEntity::getDelFlag, Boolean.FALSE)
                .list();
    }


    @Override
    public Integer countSerialNumberIsNull(String infoId) {
        Assert.notBlank(infoId, "证书信息不存在");
        return this.lambdaQuery()
                .eq(ZsExamineeInfo::getZsId, infoId)
                .isNull(ZsExamineeInfo::getCertificateSerialNumber)
                .eq(BaseEntity::getDelFlag, Boolean.FALSE)
                .count();
    }

    @Override
    public List<ZsExamineeInfo> listByIdNo(String idNo, String infoId) {
        return this.lambdaQuery()
                .eq(ZsExamineeInfo::getZsId, infoId)
                .eq(ZsExamineeInfo::getIdentificationNo, idNo)
                .eq(BaseEntity::getDelFlag, Boolean.FALSE)
                .list();
    }

    @Override
    public Boolean exitCertificateSerialNumber(String zsId, String id, String certificateSerialNumber) {
        log.info("## exitCertificateSerialNumber {},{},{},{}", zsId, id, certificateSerialNumber);
        List<ZsExamineeInfo> zsExamineeInfos = this.lambdaQuery()
                .eq(ZsExamineeInfo::getZsId, zsId)
                .ne(ZsExamineeInfo::getId, id)
                .eq(ZsExamineeInfo::getCertificateSerialNumber, certificateSerialNumber)
                .eq(BaseEntity::getDelFlag, Boolean.FALSE)
                .list();
        log.info("## exitCertificateSerialNumber zsExamineeInfos: {}", JSONUtil.toJsonStr(zsExamineeInfos));
        return CollUtil.isNotEmpty(zsExamineeInfos) ? Boolean.TRUE : Boolean.FALSE;
    }

    @Override
    public Boolean exitIdentificationNo(String zsId, String id, String identificationType, String identificationNo) {
        log.info("## exitIdentificationNo {},{},{},{}", zsId, id, identificationType, identificationNo);
        List<ZsExamineeInfo> zsExamineeInfos = this.lambdaQuery()
                .eq(ZsExamineeInfo::getZsId, zsId)
                .ne(ZsExamineeInfo::getId, id)
                .eq(ZsExamineeInfo::getIdentificationType, identificationType)
                .eq(ZsExamineeInfo::getIdentificationNo, identificationNo)
                .eq(BaseEntity::getDelFlag, Boolean.FALSE)
                .list();
        log.info("## exitIdentificationNo zsExamineeInfos: {}", JSONUtil.toJsonStr(zsExamineeInfos));
        return CollUtil.isNotEmpty(zsExamineeInfos) ? Boolean.TRUE : Boolean.FALSE;
    }

    @Override
    public Boolean editZsExamineeInfo(ZsExamineeInfoVO zsExamineeInfoVO) {
        log.info("## editZsExamineeInfo zsExamineeInfoVO {}", JSONUtil.toJsonStr(zsExamineeInfoVO));
        ZsExamineeInfo originExamineesInfo = Optional.ofNullable(zsExamineeInfoVO.getId())
                .map(this::getById)
                .orElseThrow(() -> new CustomException("当前操作的考生不存在"));

        ZsInfo zsInfo = Optional.ofNullable(originExamineesInfo.getZsId())
                .map(iZsInfoService::getById)
                .orElseThrow(() -> new CustomException("当前操作的证书不存在"));

        // 获取新的证书专业名称
        assembleSubjectName(zsInfo::getCertificateTemplateid,
                zsExamineeInfoVO::getCertificateSubject,
                zsExamineeInfoVO::setCertificateSubjectName);
        // 获取新的证书级别名称
        assembleLevelName(zsExamineeInfoVO::getCertificateSubject,
                zsExamineeInfoVO::getCertificateLevel,
                zsExamineeInfoVO::setCertificateLevelDesc);

        // 修改证件信息
        ZsExamineeInfo zsExamineeInfo = BeanUtil.toBean(zsExamineeInfoVO, ZsExamineeInfo.class);
        int count = zsExamineeInfoMapper.updateById(zsExamineeInfo);
        log.info("## editZsExamineeInfo count {}", count);
        return count > 0 ? Boolean.TRUE : Boolean.FALSE;
    }

    /**
     * 装配考生证书专业等级名称
     *
     * @param getCertificateSubject
     * @param getCertificateSubject
     * @param setCertificateSubjectDesc
     */
    private void assembleSubjectName(Supplier<String> getCertificateTemplate, Supplier<String> getCertificateSubject, Consumer<String> setCertificateSubjectDesc) {
        Optional.ofNullable(getCertificateTemplate)
                .map(Supplier::get)
                .ifPresent(templateId -> {
                    List<SpecialityDTO> specialityDTOS = templateService.listSpecialities(templateId);
                    String subjectName = specialityDTOS.stream()
                            .filter(data -> getCertificateSubject.get().equals(data.getSpecialityValue()))
                            .findFirst()
                            .map(SpecialityDTO::getSpecialityName)
                            .orElseThrow(() -> new CustomException("无效的证书专业"));
                    setCertificateSubjectDesc.accept(subjectName);
                });
    }

    /**
     * 装配考生证书专业等级名称
     *
     * @param getCertificateSubject
     * @param getCertificateLevel
     * @param setCertificateLevelDesc
     */
    private void assembleLevelName(Supplier<String> getCertificateSubject, Supplier<String> getCertificateLevel, Consumer<String> setCertificateLevelDesc) {
        Optional.ofNullable(getCertificateSubject)
                .map(Supplier::get)
                .ifPresent(specialityValue -> {
                    List<LevelDTO> levelDTOS = templateService.listLevels(specialityValue);
                    String levelName = levelDTOS.stream()
                            .filter(data -> getCertificateLevel.get().equals(data.getLevelValue()))
                            .findFirst()
                            .map(LevelDTO::getLevelName)
                            .orElseThrow(() -> new CustomException("无效的证书级别"));
                    setCertificateLevelDesc.accept(levelName);
                });
    }

    @Override
    public Boolean checkCertificateProvideStatus(String zsId, String id, Integer provideStatus) {
        ZsExamineeInfo zsExamineeInfo = this.lambdaQuery()
                .eq(ZsExamineeInfo::getZsId, zsId)
                .eq(ZsExamineeInfo::getId, id)
                .eq(ZsExamineeInfo::getCertificateProvideStatus, provideStatus)
                .eq(BaseEntity::getDelFlag, Boolean.FALSE)
                .one();
        return null == zsExamineeInfo ? Boolean.FALSE : Boolean.TRUE;
    }

    @Override
    public Integer countPublishNumByZsId(String zsId, Integer provideStatus) {
        log.info("## countPublishNumByZsId {},{}", zsId, provideStatus);
        Integer count = zsExamineeInfoMapper.countPublishNumByZsId(zsId, provideStatus);
        log.info("## countPublishNumByZsId count,{}", count);
        return count;
    }

    @Override
    public Boolean recallCertificate(String examineesInfoId) {
        ZsExamineeInfo examineesInfo = Optional.ofNullable(getById(examineesInfoId))
                .orElseThrow(() -> new CustomException("当前操作的考生不存在"));
        if (examineesInfo.getDelFlag()) {
            throw new CustomException("不允许操作已删除的考生证书");
        }
        if (!CertificateProvideStatusEnums.PROVIDED.getCode().equals(examineesInfo.getCertificateProvideStatus())) {
            throw new CustomException("不允许操作不为已发放状态的证书");
        }
        return this.lambdaUpdate()
                .set(ZsExamineeInfo::getCertificateImgUrl, null)
                .set(ZsExamineeInfo::getCertificatePdfUrl, null)
                .set(ZsExamineeInfo::getCertificateProvideStatus, CertificateProvideStatusEnums.UNPROVIDED.getCode())
                .set(ZsExamineeInfo::getUpdateBy, loginService.getUserId())
                .set(ZsExamineeInfo::getUpdateDate, LocalDateTime.now())
                .eq(BaseEntity::getId, examineesInfoId)
                .update();
    }

    @Override
    public ZsExamineeInfo getLastOneStudentByZsId(String zsId) {
        ZsExamineeInfo zsExamineeInfo = zsExamineeInfoMapper.getLastOneStudentByZsId(zsId);
        return zsExamineeInfo;
    }

    @Override
    public List<ZsExamineeInfo> listByCanViewByIds(List<String> zsExamineeIds) {
        return this.lambdaQuery()
                .eq(BaseEntity::getDelFlag, Boolean.FALSE)
                .in(BaseEntity::getId, zsExamineeIds)
                .eq(ZsExamineeInfo::getCertificateProvideStatus, CertificateProvideStatusEnums.PROVIDED.getCode())
                .list();
    }

    @Override
    public List<ZsTemplateConfig> showViewHead(String zsId) {
        ZsInfo zsInfo = iZsInfoService.getById(zsId);
        if (zsInfo == null) {
            throw new CustomException("无效的证书信息");
        }
        // 获取模板列表渲染配置
        List<ZsTemplateConfig> zsTemplateConfigs = templateConfigService.listShowViewConfig(zsInfo.getCertificateTemplateid());

        return zsTemplateConfigs;
    }

}
